#include "configRL.hpp"
#include "../TempControl/tempUpdate.hpp"
#include "../main/tempCtrlService.hpp"

#include "../Global/GlobalDefs.hpp"
#include "../Global/globalVars.hpp"
#include "../Interface/report.hpp"

#include <Arduino.h>
// uMicro cannot find this esp32 lib
#include"Preferences/Preferences.h"

// instead of eeprom, use preferences.h as a higher-level nvs lib
//#include <Preferences.h>
// #include <arduino>

/*
Metadata
*/
#define META_NAME_SPACE "meta"
#define KEY_META_IDENTIFIER "ID"

/*
Start up
*/

#define START_UP_NAME_SPACE "startup"

#define KEY_START_UP_CONTROL_MODE "SUCM"
#define KEY_START_UP_CONTROL_TARGET_TEMP "SUCTT"
#define KEY_START_UP_VERBOSE "SUV"

/*
Sensor calibration
*/
#define SENSOR_NAME_SPACE "sensors"
#define KEY_PLATE_NTC_PT_VOLT_MV "PNV"
#define KEY_PLATE_NTC_PT_TEMP_K "PNT"
#define KEY_PLATE_NTC_PT_NUM "PNN"

#define KEY_DRIVE_NTC_B_VALUE "DNBV"
#define KEY_DRIVE_NTC_REFERENCE_RESISTANCE "DNRR"

#define KEY_SURROUNDING_NTC_B_VALUE "SNBV"
#define KEY_SURROUNDING_NTC_REFERENCE_RESISTANCE "SNRR"

#define KEY_LID_NTC_B_VALUE "LNBV"
#define KEY_LID_NTC_REFERENCE_RESISTANCE "LNRR"

/*
Temperature control feedback loop
*/
#define HEAT_UP_NAME_SPACE "heat-up"
#define HEAT_HOLD_NAME_SPACE "heat-hd"
#define COOL_DOWN_NAME_SPACE "cool-dn"
#define COOL_HOLD_NAME_SPACE "cool-hd"

#define HEAT_PID_NAME_SPACE "heat-pid"
#define COOL_PID_NAME_SPACE "cool-pid"

#define CURVE_PT_NUM "CVPN"
#define CURVE_PT_LEVELS "CVPL"
#define CURVE_PT_TEMPERATURE_DIFFS "CVPTD"

#define CHANGE_SPEED "CGSD"
#define SPEED_ENTEY "SPEED"

#define PID_ENTRY "PID"

/*
Temperature control - thermal modeling
*/
#define THERMAL_MODEL_NAME_SPACE "thermal_model"

#define KEY_plateLidSteadyStateDiff "PLSSD"
#define KEY_plateToHolderHeatCapacityRatio "PHHCR"

Preferences preferences;

void ConfigRL::MetaDataRL::set(const char* identifier, bool feedback) {
	GlobalVars::identifier = identifier;
	if (preferences.begin(META_NAME_SPACE, false)) {
		preferences.putString(KEY_META_IDENTIFIER, identifier);

		preferences.end();
		if (feedback) {
			Serial.println("Set metadata success");
			Serial.print(Report::reportMetadata());
			Serial.flush();
		}
	}
	else {
		if (feedback) {
			Serial.println("Cannot edit meta data");
		}
	}
}

void ConfigRL::MetaDataRL::load() {
	if (preferences.begin(META_NAME_SPACE, true)) {
		GlobalVars::identifier = preferences.getString(KEY_META_IDENTIFIER, String(DEV_MODEL));
		preferences.end();
		Serial.println("Loaded metadata");
	}
	else {
		Serial.println("Cannot find metadata");
	}
}

void ConfigRL::StartUpModeRL::set(int startUpMode, float startUpTarget, int startUpVerbose, bool feedback) {
	GlobalVars::OnBoot::controlMode = startUpMode;
	GlobalVars::OnBoot::tempTarget = startUpTarget;
	GlobalVars::OnBoot::verbose = startUpVerbose;
	if (preferences.begin(START_UP_NAME_SPACE, false)) {
		preferences.putInt(KEY_START_UP_CONTROL_MODE, GlobalVars::OnBoot::controlMode);
		preferences.putFloat(KEY_START_UP_CONTROL_TARGET_TEMP, GlobalVars::OnBoot::tempTarget);
		preferences.putInt(KEY_START_UP_VERBOSE, GlobalVars::OnBoot::verbose);
		preferences.end();

		if (feedback) {
			Serial.println("Set start up mode");
			Serial.print(Report::reportStartUpConfig());
			Serial.flush();
		}
	}
	else {
		if (feedback) {
			Serial.println("Cannot edit start up mode");
		}
	}
}

void ConfigRL::StartUpModeRL::load() {
	if (preferences.begin(START_UP_NAME_SPACE, true)) {
		GlobalVars::OnBoot::controlMode = preferences.getInt(KEY_START_UP_CONTROL_MODE, 0);
		GlobalVars::OnBoot::tempTarget = preferences.getFloat(KEY_START_UP_CONTROL_TARGET_TEMP, 0);
		GlobalVars::OnBoot::verbose = preferences.getInt(KEY_START_UP_VERBOSE, 0);
		preferences.end();
		Serial.println("Loaded start up mode");
	}
	else {
		Serial.println("Cannot find start up mode");
	}
}

void save_curve_core(BangBangController& curve) {
	preferences.putUChar(CURVE_PT_NUM, curve.curvePtNum);
	preferences.putBytes(CURVE_PT_LEVELS,
		curve.ptDrivePercent,
		curve.curvePtNum * sizeof(uint8_t));
	preferences.putBytes(CURVE_PT_TEMPERATURE_DIFFS,
		curve.ptTempDiffC,
		curve.curvePtNum * sizeof(float));
}

void load_curve_core(BangBangController& curve) {
	preferences.getUChar(CURVE_PT_NUM, curve.curvePtNum);
	preferences.getBytes(CURVE_PT_LEVELS,
		curve.ptDrivePercent,
		curve.curvePtNum * sizeof(uint8_t));
	preferences.getBytes(CURVE_PT_TEMPERATURE_DIFFS,
		curve.ptTempDiffC,
		curve.curvePtNum * sizeof(float));
}

typedef const char* ReportCall();
void save_curve_template(const char* name, BangBangController* curve, ReportCall reportCall, bool feedback) {
	if (feedback) {
		Serial.print("To save ");
		Serial.print(name);
		Serial.println(" curve");
	}
	if (preferences.begin(name, false))
	{
		save_curve_core(*curve);

		preferences.end();
		if (feedback) {
			Serial.println("Success");

			if (GlobalVars::flagVerboseMode)
			{
				Serial.println(reportCall());
			}
		}
	}
}

void load_curve_template(const char* name, BangBangController* curve, ReportCall reportCall) {
	Serial.print("To load ");
	Serial.print(name);
	Serial.println(" curve");
	if (preferences.begin(name, true))
	{
		load_curve_core(*curve);

		preferences.end();
		Serial.println("Success");
	}
	else
	{
		Serial.print("Cannot load ");
		Serial.print(name);
		Serial.println(" curve");
	}
}

void save_PID_template(const char* name, PIDController* paras, ReportCall reportCall, bool feedback) {
	if (feedback) {
		Serial.print("To save ");
		Serial.print(name);
		Serial.println(" PID weights");
	}
	if (preferences.begin(name, false))
	{
		float pidArray[3] = { paras->kp, paras->ki, paras->kd };
		preferences.putBytes(PID_ENTRY, pidArray, sizeof(pidArray));

		preferences.end();
		if (feedback) {
			Serial.println("Success");

			if (GlobalVars::flagVerboseMode)
			{
				Serial.println(reportCall());
			}
		}
	}
}

void load_PID_template(const char* name, PIDController* paras, ReportCall reportCall) {
	Serial.print("To load ");
	Serial.print(name);
	Serial.println(" PID weights");
	if (preferences.begin(name, true))
	{
		float pidArray[3] = { paras->kp, paras->ki, paras->kd };
		if (preferences.getBytes(PID_ENTRY, pidArray, sizeof(pidArray)) > 0) {
			paras->kp = pidArray[0];
			paras->ki = pidArray[1];
			paras->kd = pidArray[2];
		}

		preferences.end();
		Serial.println("Success");
	}
	else
	{
		Serial.print("Cannot load ");
		Serial.print(name);
		Serial.println(" curve");
	}
}

void ConfigRL::SensorCaliRL::load()
{
	if (preferences.begin(SENSOR_NAME_SPACE, true))
	{
		uint8_t plateNtcCaliPtNum = preferences.putUChar(KEY_PLATE_NTC_PT_NUM, 0);
		if (plateNtcCaliPtNum > 0 && plateNtcCaliPtNum < 10)
		{
			float* buf = new float[plateNtcCaliPtNum];
			preferences.getBytes(KEY_PLATE_NTC_PT_VOLT_MV, buf, plateNtcCaliPtNum * sizeof(float));
			TempReader::setPlateTmpCaliTmpKs(buf, plateNtcCaliPtNum);
			preferences.getBytes(KEY_PLATE_NTC_PT_TEMP_K, buf, plateNtcCaliPtNum * sizeof(float));
			TempReader::setPlateTmpCaliVolts(buf, plateNtcCaliPtNum);
			delete[] buf;
		}

		TempReader::setSurroundingTmpNTCBR(preferences.getFloat(KEY_SURROUNDING_NTC_B_VALUE, -1), preferences.getFloat(KEY_SURROUNDING_NTC_REFERENCE_RESISTANCE, -1));
		TempReader::setLidTmpNTCBR(preferences.getFloat(KEY_LID_NTC_B_VALUE, -1), preferences.getFloat(KEY_LID_NTC_REFERENCE_RESISTANCE, -1));

		preferences.end();
	}
}

void ConfigRL::SensorCaliRL::writePlate(float* temps, float* volts, int valueNum)
{
	if (preferences.begin(SENSOR_NAME_SPACE, false))
	{
		if (valueNum > 10)
		{
			Serial.println("[ConfigRL] At most 10 points for plate cali");
		}
		else
		{
			preferences.putBytes(KEY_PLATE_NTC_PT_TEMP_K, temps, valueNum * sizeof(float));
			preferences.putBytes(KEY_PLATE_NTC_PT_VOLT_MV, volts, valueNum * sizeof(float));
		}
		preferences.end();
	}
}

void ConfigRL::SensorCaliRL::writeSurrounding(float surB, float surR)
{
	if (preferences.begin(SENSOR_NAME_SPACE, false))
	{
		preferences.putFloat(KEY_SURROUNDING_NTC_B_VALUE, surB);
		preferences.putFloat(KEY_SURROUNDING_NTC_REFERENCE_RESISTANCE, surR);

		preferences.end();
	}
}

void ConfigRL::ThermalModelMetricsRL::load()
{
	GlobalVars::plateTemp.setSimuThermalModel(0.1, 0.03, true);
	GlobalVars::sampleLeftTemp.setSimuThermalModel(0.1, 0.04, true);
	GlobalVars::sampleRightTemp.setSimuThermalModel(0.1, 0.04, true);
#ifdef MODEL_TEC_CYCLER
	GlobalVars::pumpTemp.setSimuThermalModel(0.1, 0.1, true);
#else
	GlobalVars::pumpTemp.setSimuThermalModel(0.03, 0.1, false);
#endif
	GlobalVars::containerTemp.setSimuThermalModel(0.8, 0.05);
	GlobalVars::surroundingTemp.setSimuThermalModel(0, 0);
	// determined by output power
	GlobalVars::coreTemp.setSimuThermalModel(0.01, 0.01, true);

	Serial.println("To load thermalModelMetrics");
	if (preferences.begin(THERMAL_MODEL_NAME_SPACE, true))
	{
		//GlobalVars::thermalModelMetrics.plateLidSteadyStateDiff = preferences.getFloat(KEY_plateLidSteadyStateDiff, 0);
		GlobalVars::thermalModel.plateToHolderHeatCapacityRatio = preferences.getFloat(KEY_plateToHolderHeatCapacityRatio, 1);
		preferences.end();

		Serial.println("Success");

		if (GlobalVars::flagVerboseMode)
		{
			Serial.println(Report::reportThermalModel());
		}
	}
}

void ConfigRL::ThermalModelMetricsRL::save(bool feedback)
{
	if (feedback) {
		Serial.println("To save thermalModelMetrics");
	}
	if (preferences.begin(THERMAL_MODEL_NAME_SPACE, false))
	{
		//preferences.putFloat(KEY_plateLidSteadyStateDiff, GlobalVars::thermalModelMetrics.plateLidSteadyStateDiff);
		preferences.putFloat(KEY_plateToHolderHeatCapacityRatio, GlobalVars::thermalModel.plateToHolderHeatCapacityRatio);
		preferences.end();
		if (feedback) {
			Serial.println("Success");
		}
	}
	else
	{
		if (feedback) {
			Serial.println("Cannot save thermalModelMetrics");
		}
	}
}

void ConfigRL::ChangeTempSpeedRL::load()
{
	Serial.println("To load ChangeTempSpeed");
	if (preferences.begin(CHANGE_SPEED, true))
	{
		GlobalVars::ctrlPlan.ChangeTempSpeed = preferences.getFloat(SPEED_ENTEY, 1);
		preferences.end();
		Serial.println("Success");
		if (GlobalVars::flagVerboseMode)
		{
			Serial.println(Report::reportChangeSpeed());
		}
	}
}

void ConfigRL::ChangeTempSpeedRL::save(bool feedback)
{
	if (feedback) {
		Serial.println("To save ChangeTempSpeed");
	}
	if (preferences.begin(CHANGE_SPEED, false))
	{
		preferences.putFloat(SPEED_ENTEY, GlobalVars::ctrlPlan.ChangeTempSpeed);
		preferences.end();
		if (feedback) {
			Serial.println("Success");
		}
	}
	else
	{
		if (feedback) {
			Serial.println("Cannot save ChangeTempSpeed");
		}
	}
}

void ConfigRL::HeadUpCurveRL::load()
{
#ifdef CONTROL_BANGBANG
	load_curve_template(HEAT_UP_NAME_SPACE, (BangBangController*)GlobalVars::heatUpCurve, Report::reportHeatUpConfig);
#endif
#ifdef CONTROL_MODE_PID
	load_PID_template(HEAT_UP_NAME_SPACE, (PIDController*)GlobalVars::heatUpCurve, Report::reportHeatUpConfig);
#endif
}

void ConfigRL::HeadUpCurveRL::save(bool feedback)
{
#ifdef CONTROL_BANGBANG
	save_curve_template(HEAT_UP_NAME_SPACE, (BangBangController*)GlobalVars::heatUpCurve, Report::reportHeatUpConfig, feedback);
#endif
#ifdef CONTROL_MODE_PID
	save_PID_template(HEAT_UP_NAME_SPACE, (PIDController*)GlobalVars::heatUpCurve, Report::reportHeatUpConfig, feedback);
#endif
}

void ConfigRL::HeadHoldCurveRL::load()
{
#ifdef CONTROL_BANGBANG
	load_curve_template(HEAT_HOLD_NAME_SPACE, (BangBangController*)GlobalVars::heatHoldCurve, Report::reportHeatHoldConfig);
#endif
#ifdef CONTROL_MODE_PID
	// same as heat up
	load_PID_template(HEAT_HOLD_NAME_SPACE, (PIDController*)GlobalVars::heatHoldCurve, Report::reportHeatUpConfig);
#endif
}

void ConfigRL::HeadHoldCurveRL::save(bool feedback)
{
#ifdef CONTROL_BANGBANG
	save_curve_template(HEAT_HOLD_NAME_SPACE, (BangBangController*)GlobalVars::heatHoldCurve, Report::reportHeatHoldConfig, feedback);
#endif
#ifdef CONTROL_MODE_PID
	save_PID_template(HEAT_HOLD_NAME_SPACE, (PIDController*)GlobalVars::heatHoldCurve, Report::reportHeatUpConfig, feedback);
#endif
}

void ConfigRL::CoolDownCurveRL::load()
{
#ifdef CONTROL_BANGBANG
	load_curve_template(COOL_DOWN_NAME_SPACE, (BangBangController*)GlobalVars::coolDownCurve, Report::reportCoolDownConfig);
#endif
#ifdef CONTROL_MODE_PID
	// same as heat up
	load_PID_template(COOL_DOWN_NAME_SPACE, (PIDController*)GlobalVars::coolDownCurve, Report::reportCoolDownConfig);
#endif
}

void ConfigRL::CoolDownCurveRL::save(bool feedback)
{
#ifdef CONTROL_BANGBANG
	save_curve_template(COOL_DOWN_NAME_SPACE, (BangBangController*)GlobalVars::coolDownCurve, Report::reportCoolDownConfig, feedback);
#endif
#ifdef CONTROL_MODE_PID
	save_PID_template(COOL_DOWN_NAME_SPACE, (PIDController*)GlobalVars::coolDownCurve, Report::reportHeatUpConfig, feedback);
#endif
}

void ConfigRL::CoolHoldCurveRL::load()
{
#ifdef CONTROL_BANGBANG
	load_curve_template(COOL_HOLD_NAME_SPACE, (BangBangController*)GlobalVars::coolHoldCurve, Report::reportCoolHoldConfig);
#endif
#ifdef CONTROL_MODE_PID
	// same as heat up
	load_PID_template(COOL_HOLD_NAME_SPACE, (PIDController*)GlobalVars::coolHoldCurve, Report::reportCoolHoldConfig);
#endif
}

void ConfigRL::CoolHoldCurveRL::save(bool feedback)
{
#ifdef CONTROL_BANGBANG
	save_curve_template(COOL_HOLD_NAME_SPACE, (BangBangController*)GlobalVars::coolHoldCurve, Report::reportCoolHoldConfig, feedback);
#endif
#ifdef CONTROL_MODE_PID
	save_PID_template(COOL_HOLD_NAME_SPACE, (PIDController*)GlobalVars::coolHoldCurve, Report::reportHeatUpConfig, feedback);
#endif
}

void ConfigRL::MiscFlagsRL::load() {

};

void ConfigRL::MiscFlagsRL::save(bool feedback) {

};